#!/bin/env python2.5.1

import Dingo.API as api
import random
import time
import copy
import sets #For dynamic updates

#Static variables
sink = "Sink[-1]"   #Sink node ID
ss_slot_time = 0.2  #
P = 0.1           #The desired percentage of CH's

class sinkRouting:
    def __init__(self, sensor, NUMSENSOR):
        self.sensor = sensor
        self.NUMSENSOR = NUMSENSOR

    def StartUp(self):  #Initialize the protocol
        NUMSENSOR = self.NUMSENSOR
        if self.sensor.id == "Sink[-1]" and self.sensor.state == "Sink":
            self.sensor.state = "DSink" #Change state

            #Flood the network
            self.sensor.debug("RADIO: %s    send    INICH" % self.sensor.id)
            self.packet = APacket(self.sensor.id, None, ["INICH"])
            ### Put packet on send queue
            self.sensor.send_q.put(self.packet) 
            ### Send next packet in queue with arc colour
            self.sensor.send(api.SensorColor.FromName("Red"))

    def receiveTask(self,themsg):
        self.themsg = themsg
        mType = self.themsg.msg[0]  #Type of message received

        if mType == "ToS":
            self.sensor.points.append(self.themsg.msg[1])
            self.sensor.points = list(sets.Set(self.sensor.points)) #Remove duplicates ONLY work for high map data
            self.sensor.debug("SIM: The received data to time %s is:\n %s" % (timeStamp(), self.sensor.points))

class sensorRouting:
    def __init__(self, sensor):
        self.sensor = sensor
        
        #Local variables
        self.tcolor = "Green"   #Temp color cell
        self.seqNum = self.sensor.number + 10  #Query unique message Id

    def receiveTask(self,themsg):
        self.themsg = themsg

        mType = self.themsg.msg[0]  #Type of message received
        
        #-----Receive broadcast message INICH
        if self.themsg.receiver_id == None and mType == "INICH" and self.sensor.state == "Waiting":
            
            self.sensor.state = "Actuator"  #Change state
            self.sensor.setColour(api.SensorColor.FromName("Green")) #Change colour
            self.sensor.s_Path = self.themsg.sender_id #Set path to the sink

            self.sensor.r = 1     #Update round number

            ran = random.random()     #Return the next random floating point number in the range [0.0, 1.0]
            self.sensor.debug("SIM: random number value %s" % ran)
            #calculate threshould value
            tn = P/((1-P)*(self.sensor.r%(1/P)))
            self.sensor.debug("SIM: tn value: %s" % tn)
            if ran < tn:
                #Start cluster formation
                self.startCluster(api.SensorColour.FromRGBCombined(random.randrange(1<<24)))

        #-----Receive broadcast message ADV_CH and node state is Actuator
        elif self.themsg.receiver_id == None and not self.sensor.isCH and mType == "ADV_CH" and self.sensor.state == "Actuator" and self.sensor.myCH == '-':

            self.tcolor = self.themsg.msg[2]    #Copy cluster colour
            self.sensor.myCH = self.themsg.msg[1]   #Copy ID of advertising CH
            self.sensor.lastNode = self.themsg.sender_id    #Copy ID of next node to CH
            self.CheckTime()

        #-----When a node receives a join request message
        elif self.themsg.receiver_id == self.sensor.id and mType == "JOIN_REQ" and self.sensor.state == "Actuator":

            self.origin = self.themsg.sender_id

            #When CH receive the JOIN_REQ message
            if self.sensor.isCH:
                #If not already registered as member
                if self.origin not in self.sensor.members:
                    self.sensor.members.append(self.origin)    #Copy sender to cluster members list


        #-----Deals with tdma sechdule advertisment
        elif mType == "ADV_SCH" and not self.sensor.isCH and self.themsg.msg[1] == self.sensor.myCH and self.themsg.msg[2] not in self.sensor.seenADV_SCH and self.sensor.state == "Actuator":
            self.sensor.seenADV_SCH.append(self.themsg.msg[2])
            self.order = self.themsg.msg[3]

            if str(self.sensor.id) in self.order:
                self.ind = self.order.index(self.sensor.id)
                self.xmitat = self.ind * ss_slot_time
                self.sensor.debug("SIM: %s scheduled to transmit after %s.  It is now %s" % (self.sensor.id, self.xmitat, timeStamp()))
                api.Sleep(self.xmitat)
                self.sendData()
            else:
                self.sensor.debug("SIM: Warning!!!!  %s does not have a transmit time!" % self.sensor.id)
                self.sensor.debug("SIM: Must transmit data directly to BS.")
                self.sensor.debug("RADIO: %s    send    ToS" % self.sensor.id)
                self.packet = APacket(self.sensor.id, self.sensor.s_Path, ["ToS", self.sensor.data])
                self.sendPacket(self.packet, "Yellow")

        #Handle sending of data to CH then to sink
        elif self.themsg.receiver_id == self.sensor.id and mType == "Data" and self.sensor.state == "Actuator":
            #When node CH receives the node's data message
            if self.themsg.msg[2] == self.sensor.id and self.sensor.isCH and self.themsg.msg[1] not in self.sensor.AGdata:
                self.sensor.AGdata.append(self.themsg.msg[1])
                if len(self.sensor.AGdata) == len(self.sensor.members):
                    self.dataToSink()
                    self.sensor.AGdata = []
                self.sensor.debug("SIM: CH %s recieved data message from node %s at time %s" %(self.sensor.id, self.themsg.sender_id, timeStamp()))


    def startCluster(self, colour):
        self.sensor.setColour(api.SensorColor.FromName("Blue")) #Blue for CH nodes
        self.sensor.isCH = True
        self.sensor.scheduleTime = timeStamp()  #Set TDMA creation time
        self.sensor.timestamp = timeStamp() #Set cluster start time
        self.colour = colour
        self.sensor.checktdma = True    #After advertisment start checking
        self.sensor.scheduleTime = timeStamp()  #Start timer
        self.sensor.g = self.sensor.r
        
        self.sensor.debug("RADIO: %s    send    ADV_CH" % self.sensor.id)
        self.packet = APacket(self.sensor.id, None, ["ADV_CH", self.sensor.id, self.colour, 0])
        self.broadcastPacket(self.packet, "Blue")

    def createTDMA(self):
        self.sensor.tdma = False
        if len(self.sensor.members) == 0:
            self.sensor.debug("SIM: Warning!  There are no nodes in this cluster %s!" % self.sensor.id)
        else:
            #Set the TDMA schedule and send it to all nodes in the cluster
            self.TDMAschedule = self.sensor.members
            self.sensor.scheduleTime = timeStamp()
            self.sequnceNumber = int(random.uniform(101,150))

            self.sensor.debug("RADIO: %s    send    TDMA schedule: %s" % (self.sensor.id, self.sensor.members))
            self.packet = APacket(self.sensor.id, None, ["ADV_SCH", self.sensor.id, self.sequnceNumber, self.TDMAschedule])
            self.broadcastPacket(self.packet, "Blue")

    def reply(self):
        #Calculate wish value by equation latter
        wishValue = random.randrange(50)
        self.load = []
        self.load.append(self.sensor.id)

        self.sensor.debug("RADIO: %s    send    JOIN_REQ" % self.sensor.id)
        self.packet = APacket(self.sensor.id, self.sensor.myCH, ["JOIN_REQ", wishValue, self.load])
        self.sendPacket(self.packet, "LightBlue")

    def dataToSink(self):
        self.sensor.debug("RADIO: %s    send    ToS at time %s" % (self.sensor.id, timeStamp()))
        self.packet = APacket(self.sensor.id, self.sensor.s_Path, ["ToS", self.sensor.AGdata])
        self.sendPacket(self.packet, "Yellow")

    def emptyAGdata(self):
        if self.sensor.isCH:
            self.dataToSink()
        else: #Make it ready for sensor node aggregation (maybe needed in the future)
            self.sensor.debug("RADIO: %s    send    Data at time %s" % (self.sensor.id, timeStamp()))
            self.packet = APacket(self.sensor.id, self.sensor.myCH, self.sensor.data)
            self.sendPacket(self.packet, "Black")

        self.sensor.AGdata = []


    #Method to join a CH after the back-off time ends
    def CheckTime(self):
        self.sensor.setColour(self.tcolor) #Set colour
        self.sensor.timestamp = timeStamp() #Set start time of cluster round
        self.reply()    #Send join request


    def checkExpires(self):
        self.sensor.r = self.sensor.r + 1     #update round number

        if self.sensor.isCH: #Cluster head node
            self.sensor.debug("SIM: %s end of current cluster round at time %s" % (self.sensor.id, timeStamp()))
            self.sensor.isCH = False
            self.sensor.members = []
            self.sensor.points = []
            self.sensor.scheduleTime = timeStamp()
            self.sensor.timestamp = timeStamp()
            self.sensor.myCH = '-'
            self.lastNode = '-'
            self.tcolor = "Green"
            self.sensor.setColour(api.SensorColor.FromName("Green")) #Change state

        else: #Sensing node
            self.sensor.timestamp = timeStamp()
            self.sensor.myCH = '-'
            self.lastNode = '-'
            self.sensor.points = []
            self.sensor.scheduleTime = timeStamp()
            self.tcolor = "Green"
            self.sensor.r = self.sensor.r + 1
            
            ran = random.random()     #Return the next random floating point number in the range [0.0, 1.0]
            #calculate threshould value
            if self.sensor.r-self.sensor.g >= 1/P:
                tn = P/((1-P)*(self.sensor.r%(1/P)))
            else:
                tn = 0

            if ran < tn:
                #Start cluster formation
                self.startCluster(api.SensorColour.FromRGBCombined(random.randrange(1<<24)))


    def sendData(self): #Create data packet, each node use its address as message load
        if self.sensor.isCH:
            self.sensor.points.append(self.sensor.data)
        else:
            self.sensor.debug("RADIO: %s    send    Data    at time    %s" % (self.sensor.id, timeStamp()))
            self.packet = APacket(self.sensor.id, self.sensor.myCH, ["Data", self.sensor.data, self.sensor.myCH])
            self.sendPacket(self.packet, "Black")
    
    def sendPacket(self, packet, colour):
        self.colour = colour
        self.packet = packet
        self.sensor.send_q.put(self.packet)
        self.sensor.send(api.SensorColor.FromName(self.colour))

    def broadcastPacket(self, packet, colour):
        self.colour = colour
        self.packet = packet
        ### Put packet on send queue
        self.sensor.send_q.put(self.packet) 
        ### Send next packet in queue with arc colour
        self.sensor.send(api.SensorColor.FromName(self.colour))


#Packet can be used for all kind of transmissions since receiver is optional
class APacket(api.Packet):
	def __init__(self, sender, rec, data):
		api.Packet.__init__(self, sender, rec, data)
		return

#Dan code
class timeStamp:
    def __init__(self, expires = 0):
        #Helper method to set timestamps on objects.
        added = (time.time() + expires)
        foo = time.localtime(added)
        self.hour = foo.tm_hour
        self.min =  foo.tm_min
        self.sec =  foo.tm_sec
            
    def __repr__(self):
        return "<time>(%i:%i:%i)" %(self.hour,self.min,self.sec)

    def diff(self,other):
        #Returns time Difference in Seconds in form OTHER - Given
        h = (other.hour- self.hour) *60 *60 
        m = (other.min - self.min) *60
        s = (other.sec - self.sec)
        return h+m+s

if __name__ == "__main__":
    print "Starting Simulation"